#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <stdint.h>
#ifdef _WIN32
#include <Windows.h>
#include <io.h>
#include <direct.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#endif//_WIN32

#include "SilmLog.h"
#include "SilmLogUtil.h"

#ifndef MAX_PATH
#define MAX_PATH        1024
#endif//MAX_PATH

static bool InnerIsQueueEmpty(const NAME_QUEUE &queue)
{
    return (0 == queue.count);
}

static bool InnerIsQueueFull(const NAME_QUEUE &queue)
{
    return (queue.count == MAX_LOGFILE_COUNT - 1);
}

static void InnerDeQueue(NAME_QUEUE &queue)
{
    if (!InnerIsQueueEmpty(queue))
    {
        assert(NULL != queue.logFilename[queue.front]);
        remove(queue.logFilename[queue.front]);
        free(queue.logFilename[queue.front]);
        queue.logFilename[queue.front] = NULL;
        queue.front = (queue.front + 1)%(MAX_LOGFILE_COUNT - 1);
        queue.count--;
    }
}

static void InnerEnQueue(NAME_QUEUE &queue, char *filename)
{
    if (InnerIsQueueFull(queue))
    {
        InnerDeQueue(queue);//remove the old log file
    }
    assert(NULL == queue.logFilename[queue.rear]);
    queue.logFilename[queue.rear] = filename;
    queue.rear = (queue.rear + 1)%(MAX_LOGFILE_COUNT - 1);
    queue.count++;
}

static int InnerGetFileSize(const char *path, uint64_t &size)
{
    int ret;
#ifdef _WIN32
    __int64 _size;
    FILE *fp;

    if(NULL == path)
    {
        return EINVAL;
    }
    ret = fopen_s(&fp, path, "r");
    if(0 != ret)
    {
        return ret;
    }
    _size = _filelengthi64(_fileno(fp));
    if(-1 == _size)
    {
        ret = errno;
        fclose(fp);
        return ret;
    }
    fclose(fp);
    size = (uint64_t)_size;
    return 0;
#else
    struct stat st;

    ret = stat(path, &st);
    if (0 == ret)
    {
        if (S_ISREG(st.st_mode))
        {
            size = st.st_size;
        }
        else
        {
            ret = EISDIR;
        }
    }
    else if (ENOENT == ret)
    {
        size = 0;
        ret = 0;
    }
    return ret;
#endif//_WIN32
}

static const char *inner_findFileSeperator(const char *filename)
{
#ifdef _WIN32
    while ('\0' != *filename)
    {
        if ('\\' == *filename || '/' == *filename)
        {
            return filename;
        }
        ++filename;
    }
    return NULL;
#else
    return strchr(filename, '/');
#endif//_WIN32
}

CSilmLog::CSilmLog(void)
    : m_bitmap(0),
#ifdef _WIN32
      m_hLog(INVALID_HANDLE_VALUE),
#else
      m_logFd(-1),
#endif//_WIN32
      m_filenameLen(0), m_nameSuffixOffset(0)
{
    memset(&m_queue, 0, sizeof(NAME_QUEUE));
    memset(&m_buffer, 0, sizeof(m_buffer));
}

CSilmLog::~CSilmLog(void)
{
#ifdef _WIN32
    if(INVALID_HANDLE_VALUE != m_hLog)
    {
        FlushFileBuffers(m_hLog);
        CloseHandle(m_hLog);
        m_hLog = INVALID_HANDLE_VALUE;
    }
#else
    if (-1 != m_logFd)
    {
        fsync(m_logFd);
        close(m_logFd);
    }
#endif//_WIN32
    for (int i = 0; i < MAX_LOGFILE_COUNT; i++)
    {
        if (NULL != m_queue.logFilename[i])
        {
            free(m_queue.logFilename[i]);
        }
    }
}

#ifdef _WIN32
#define WA_FILE_SEPCH   '\\'
#define WA_FILE_SEPSTR  "\\"

#define F_OK            0
#define W_OK            2
#define R_OK            4
#define RW_OK           6
#else
#define WA_FILE_SEPCH   '/'
#define WA_FILE_SEPSTR  "/"
#endif//_WIN32

static bool inner_checkFilepathValid(const char *filepath)
{
    if(NULL == filepath || '\0' == *filepath)
    {
        return false;
    }
#ifdef _WIN32
    /*must begin with such as C:\ */
    if (strlen(filepath) <= 3)
    {
        return false;
    }
    if (!(('A' <= filepath[0] && 'Z' >= filepath[0])
        || ('a' <= filepath[0] && 'z' >= filepath[0]))
        || ':' != filepath[1] || ('\\' != filepath[2] && '/' != filepath[2]))
    {
        return false;
    }
#endif//_WIN32
    return true;
}

static int InnerCreateDirRecursive(const char *dirpath)
{
    char buffer[2048];
    unsigned int offset = 0;
    unsigned int copyLen;
    const char *pSepCh;
    const char *pBegin;

    if(!inner_checkFilepathValid(dirpath))
    {
        return EINVAL;
    }
    pSepCh = inner_findFileSeperator(dirpath);
    if(NULL == pSepCh)
    {
        pBegin = dirpath;
    }
    else
    {
#ifdef _WIN32
        offset = (unsigned int)(pSepCh - dirpath);
        strncpy(buffer, dirpath, offset);
#endif//_WIN32
        buffer[offset] = WA_FILE_SEPCH;
        offset++;
        buffer[offset] = '\0';
        pBegin = pSepCh + 1;
    }
    while('\0' != *pBegin)
    {
        pSepCh = inner_findFileSeperator(pBegin);
        if(NULL == pSepCh)
        {
            copyLen = (unsigned int)strlen(pBegin);
        }
        else if(pBegin == pSepCh)
        {
            pBegin = pSepCh + 1;
            continue;
        }
        else
        {
            copyLen = (unsigned int)(pSepCh - pBegin + 1);
        }
        if(copyLen + offset > sizeof(buffer))
        {
            return ENAMETOOLONG;
        }
        memcpy(buffer + offset, pBegin, copyLen);
        offset = offset + copyLen;
        buffer[offset] = '\0';
        if(-1 == access(buffer, F_OK))
        {
            int ret = errno;
            if(ENOENT != ret)
            {
                return ret;
            }
            if(-1 == mkdir(buffer
#ifndef _WIN32
                , S_IRWXU|S_IRWXG|S_IRWXO
#endif//_WIN32
                ))
            {
                return errno;
            }
        }
        if (NULL == pSepCh)
        {
            break;
        }
        pBegin = pSepCh + 1;
    }
    return 0;
}

int CSilmLog::Initialize(unsigned int bitmap, const char *filename)
{
    char *pSep;

    m_bitmap = bitmap;
    m_filenameLen = (unsigned int)strlen(filename);
    m_queue.logFilename[MAX_LOGFILE_COUNT-1] = (char *)malloc(m_filenameLen + 1);
    if (NULL == m_queue.logFilename[MAX_LOGFILE_COUNT-1])
    {
        return ENOMEM;
    }
    memcpy(m_queue.logFilename[MAX_LOGFILE_COUNT-1], filename, m_filenameLen + 1);
    pSep = strrchr(m_queue.logFilename[MAX_LOGFILE_COUNT-1], '.');
    if (NULL == pSep)
    {
        m_nameSuffixOffset = m_filenameLen;
    }
    else
    {
        m_nameSuffixOffset = (unsigned int)(pSep - m_queue.logFilename[MAX_LOGFILE_COUNT-1]);
    }
    ListLogFiles();
    if (CheckBackupLog())
    {
        BackupLogFile();
    }
    return OpenLogFile();
}

static int InnerFormatLogTimeAndType(char *buffer, unsigned int bufLen, unsigned int logType)
{
    int ret;
    time_t curTime;
    struct tm localTime;

    time(&curTime);
#ifdef _WIN32
    if(0 == localtime_s(&localTime, &curTime))
#else
    if(NULL != localtime_r(&curTime, &localTime))
#endif//_WIN32
    {
        localTime.tm_year += 1900;
        localTime.tm_mon += 1;
        ret = UtilSprintf(buffer, bufLen, "[%04d-%02d-%02d %02d:%02d:%02d]<%s>", localTime.tm_year,
            localTime.tm_mon, localTime.tm_mday, localTime.tm_hour, localTime.tm_min,
            localTime.tm_sec, UtilGetLogTypeString(logType));
    }
    else
    {
        ret = UtilSprintf(buffer, bufLen, "<%s>", UtilGetLogTypeString(logType));
    }
    return (ret < 0) ? 0 : ret;
}

int CSilmLog::WriteLog(unsigned int logType, const char *format, va_list args)
{
    int len = 0;
    int writeLen = 0;
    int offset = 0;
#ifdef _WIN32
    DWORD hasWriteCount = 0;
#endif//_WIN32

    if (SILM_LOG_INIT_BITMAP_CONSOLE & m_bitmap || SILM_LOG_INIT_BITMAP_FILE & m_bitmap)
    {
        m_buffer[0] = '\0';
        len = InnerFormatLogTimeAndType(m_buffer, sizeof(m_buffer), logType);
        writeLen = vsnprintf(m_buffer + len, sizeof(m_buffer) - len, format, args);
    }
    if (SILM_LOG_INIT_BITMAP_CONSOLE & m_bitmap)
    {
        printf("%s\n", m_buffer);
    }
    if (SILM_LOG_INIT_BITMAP_SYS & m_bitmap)//save system log
    {
        UtilSysLog(logType, m_buffer + len);
    }
    if (SILM_LOG_INIT_BITMAP_FILE & m_bitmap)//save file log
    {
#ifdef _WIN32
        if (INVALID_HANDLE_VALUE == m_hLog)
#else
        if (-1 == m_logFd)
#endif//_WIN32
        {
            int err = OpenLogFile();
            if (0 != err)
            {
                return err;
            }
        }
        if (writeLen > 0)
        {
            len += writeLen;
        }
        while (len > 0)
        {
#ifdef _WIN32
            hasWriteCount = 0;
            if(!WriteFile(m_hLog, m_buffer + offset, len, &hasWriteCount, NULL))
            {
                return GetLastError();
            }
            else if (hasWriteCount > 0)
            {
                len -= hasWriteCount;
                offset += hasWriteCount;
            }
#else
            writeLen = (int)write(m_logFd, m_buffer + offset, len);
            if (writeLen > 0)
            {
                len -= writeLen;
                offset += writeLen;
            }
            else
            {
                return errno;
            }
#endif//_WIN32
        }
#ifdef _WIN32
        WriteFile(m_hLog, "\r\n", 2, &hasWriteCount, NULL);
#else
        write(m_logFd, "\n", 1);
#endif//_WIN32
        if (CheckBackupLog())
        {
            if(BackupLogFile())
            {
                OpenLogFile();
            }
        }
    }
    return 0;
}

void CSilmLog::ListLogFiles(void)
{
    char dirbuf[MAX_PATH];
    char namebuf[MAX_PATH];
    char extbuf[MAX_PATH];
    char filename[MAX_PATH];
    const char *pSepCh;
#ifdef _WIN32
    HANDLE hFind;
    char buffer[1024];
    WIN32_FIND_DATAA findData;
#else
    DIR *dir;
    struct dirent *elem;
    struct stat st;
#endif//_WIN32
    
    UtilParseFilename(m_queue.logFilename[MAX_LOGFILE_COUNT-1], dirbuf, sizeof(dirbuf),
        namebuf, sizeof(namebuf), extbuf, sizeof(extbuf));
    if ('\0' == dirbuf[0] || '\0' == namebuf[0])
    {
        return;
    }
    InnerCreateDirRecursive(dirbuf);
    pSepCh = inner_findFileSeperator(m_queue.logFilename[MAX_LOGFILE_COUNT-1]);
    if (NULL == pSepCh)
    {
        pSepCh = m_queue.logFilename[MAX_LOGFILE_COUNT-1];
    }
    else
    {
        pSepCh++;
    }
#ifdef _WIN32
    UtilSprintf(buffer, sizeof(buffer), "%s\\*", dirbuf);
    hFind = FindFirstFileA(buffer, &findData);
    if (INVALID_HANDLE_VALUE == hFind) 
    {
        return;
    }
    do 
    {
        if(0 == stricmp(findData.cFileName, ".") || 0 == stricmp(findData.cFileName, ".."))
        {
            continue;
        }
        if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
            //match the filename with log filename format
            if (UtilStringBeginWith(findData.cFileName, namebuf)
                && ('\0' == extbuf[0] || UtilStringEndWith(findData.cFileName, extbuf)))
            {
                UtilSprintf(filename, sizeof(filename), "%s/%s", dirbuf, findData.cFileName);
                size_t len = strlen(filename) + 1;
                char *p = (char *)malloc(len);
                if(NULL != p)
                {
                    strcpy_s(p, len, filename);
                    InnerEnQueue(m_queue, p);
                }
            }
        }
    } while (FindNextFileA(hFind, &findData));
#else
    dir = opendir(dirbuf);
    if (NULL == dir)
    {
        return;
    }
    //only list current directory file
    while (NULL != (elem = readdir(dir)))
    {
        if(0 == strcmp(".", elem->d_name)
            || 0 == strcmp("..", elem->d_name)
            || 0 == strcmp(pSepCh, elem->d_name))
        {
            continue;
        }
        UtilSprintf(filename, sizeof(filename), "%s/%s", dirbuf, elem->d_name);
        if (0 == stat(filename, &st)
            && S_ISREG(st.st_mode))
        {
            //match the filename with log filename format
            if (UtilStringBeginWith(elem->d_name, namebuf)
                && ('\0' == extbuf[0] || UtilStringEndWith(elem->d_name, extbuf)))
            {
                char *p = (char *)malloc(strlen(filename) + 1);
                if(NULL != p)
                {
                    strcpy(p, filename);
                    InnerEnQueue(m_queue, p);
                }
            }
        }
    }
    closedir(dir);
#endif//_WIN32
}

int CSilmLog::OpenLogFile(void)
{
    int ret;

#ifdef _WIN32
    assert(INVALID_HANDLE_VALUE == m_hLog);

    m_hLog = CreateFileA(m_queue.logFilename[MAX_LOGFILE_COUNT-1], GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == m_hLog)
    {
        ret = GetLastError();
        UtilSysLog(SILM_LOG_TYPE_ERROR, "Open walog file failed!errno=%d\n", ret);
        return ret;
    }
    SetFilePointer(m_hLog, 0, NULL, FILE_END);
#else
    assert(-1 == m_logFd);

    m_logFd = open(m_queue.logFilename[MAX_LOGFILE_COUNT-1],
        O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (-1 == m_logFd)
    {
        ret = errno;
        UtilSysLog(SILM_LOG_TYPE_ERROR, "Open walog file failed!errno=%d\n", ret);
        return ret;
    }
#endif//_WIN32
    return 0;
}

bool CSilmLog::CheckBackupLog(void)
{
    uint64_t size = 0;
    InnerGetFileSize(m_queue.logFilename[MAX_LOGFILE_COUNT-1], size);
    return (size >= MAX_LOGFILE_SIZE);
}

static void InnerMakeupBakLogFilename(char *buffer, unsigned int bufLen, const char *suffix)
{
    time_t curTime;
    struct tm localTime;

    time(&curTime);
#ifdef _WIN32
    if(0 == localtime_s(&localTime, &curTime))
#else
    if(NULL != localtime_r(&curTime, &localTime))
#endif//_WIN32
    {
        localTime.tm_year += 1900;
        localTime.tm_mon += 1;
        UtilSprintf(buffer, bufLen, "_%04d%02d%02d%02d%02d%02d%s", localTime.tm_year,
            localTime.tm_mon, localTime.tm_mday, localTime.tm_hour, localTime.tm_min, localTime.tm_sec, suffix);
    }
    else
    {
        srand((unsigned int)curTime);
        UtilSprintf(buffer, bufLen, "_%d%s", rand(), suffix);
    }
}

//format of backup log filename: log_yyyyMMddHHmmss.txt
int CSilmLog::BackupLogFile(void)
{
    char *filename;
    int len;

    assert(NULL != m_queue.logFilename[MAX_LOGFILE_COUNT-1]);
    
    len = m_filenameLen + 15;
    filename = (char *)malloc(len);
    if (NULL == filename)
    {
        return ENOMEM;
    }
    memcpy(filename, m_queue.logFilename[MAX_LOGFILE_COUNT-1], len);
    InnerMakeupBakLogFilename(filename + m_nameSuffixOffset,
        len - m_nameSuffixOffset + 1, m_queue.logFilename[MAX_LOGFILE_COUNT-1] + m_nameSuffixOffset);
    //close fd of log file
#ifdef _WIN32
    if (INVALID_HANDLE_VALUE != m_hLog)
    {
        FlushFileBuffers(m_hLog);
        CloseHandle(m_hLog);
        m_hLog = INVALID_HANDLE_VALUE;
    }
#else
    if (-1 != m_logFd)
    {
        fsync(m_logFd);
        close(m_logFd);
        m_logFd = -1;
    }
#endif//_WIN32
    //backup current log file
    rename(m_queue.logFilename[MAX_LOGFILE_COUNT-1], filename);
    InnerEnQueue(m_queue, filename);
    return 0;
}
